﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Checkers.Common
{
    /// <summary>
    /// This class represents a game board.
    /// </summary>
    public class GameBoard
    {
        // Constants
        private const int k_NumOfBufferLines = 2;
        private const int k_MinLinesForPlayer = 3;
        public const char k_BoardStartColumn = 'A';
        private int m_BlackPawns;
        private int m_WhitePawns;

        // Data Members
        private GamePawn[,] m_GameBoard;

        /// <summary>
        /// Creates a board with a given size
        /// </summary>
        /// <param name="i_BoardSize">The size of the board</param>
        public GameBoard(int i_BoardSize)
        {
            int boardWidth = i_BoardSize;
            int boardHeight = i_BoardSize;

            // Allocate enough lines - using the buffer lines
            // If there isnt enough space for a buffer - add buffer lines
            if ((i_BoardSize - GameBoard.k_NumOfBufferLines) / 2 < GameBoard.k_MinLinesForPlayer)
            {
                boardHeight = i_BoardSize + GameBoard.k_NumOfBufferLines;
            }

            m_GameBoard = new GamePawn[boardHeight, boardWidth];
            InitBoard();
        }

        /// <summary>
        /// Initilizes the board
        /// </summary>
        private void InitBoard()
        {
            int height = this.Height;
            int width = this.Width;
            int evenLineSpan = 0;

            // Place all the pawns
            for (int currLine = 0; currLine < (height - GameBoard.k_NumOfBufferLines) / 2; currLine++)
            {
                // If its an even line - push one span
                evenLineSpan = (currLine + 1) % 2;

                // on each line - place a pawn, and leave an empty spot
                // push one span on every other line
                for (int currCol = 0; currCol < width; currCol += 2)
                {
                    // Place a black pawn on the 'upper' side of the board
                    m_GameBoard[currLine, currCol + evenLineSpan] = new GamePawn(eGamePawnType.Black);

                    // Place a white pawn on the 'lower' side of the board
                    // (using the current line and the size of the board to mirror the pawns)
                    m_GameBoard[height - currLine - 1, currCol + (1 - evenLineSpan)] =
                        new GamePawn(eGamePawnType.White);
                }
            }

            // Init number of pawns on the board
            // Update pawns count: the whole board, without the buffer, 
            // divided by 2 to count only 1 color,
            // and then again by 2 to count non-empty cells
            m_BlackPawns = ((height - GameBoard.k_NumOfBufferLines) * width) / 4;
            m_WhitePawns = m_BlackPawns;
        }

        // Properties

        /// <summary>
        /// Gets the height of the game board
        /// </summary>
        public int Height
        {
            get
            {
                return m_GameBoard.GetLength(0);
            }
        }

        /// <summary>
        /// Gets the width of the game board
        /// </summary>
        public int Width
        {
            get
            {
                return m_GameBoard.GetLength(1);
            }
        }

        /// <summary>
        /// Get and Sets the number of black pawns
        /// </summary>
        public int BlackPawns
        {
            get 
            { 
                return m_BlackPawns; 
            }

            set 
            { 
                m_BlackPawns = value; 
            }
        }

        /// <summary>
        /// Get and Sets the number of white pawns
        /// </summary>
        public int WhitePawns
        {
            get 
            { 
                return m_WhitePawns; 
            }

            set 
            { 
                m_WhitePawns = value; 
            }
        }

        /// <summary>
        /// Returns the pawn in the selected position
        /// </summary>
        /// <param name="row">The row of the pawn (non-zero based)</param>
        /// <param name="col">The column of the pawn (non-zero based)</param>
        /// <returns>The pawn in the selected position. NULL if the position is empty</returns>
        /// <exception cref="IndexOutOfRange">If the position is outside the board</exception>
        public GamePawn this[int i_Col, int i_Row]
        {
            get
            {
                // Check if the position is inside the board
                if ((i_Row > 0 && i_Row <= Height + GameBoard.k_NumOfBufferLines) && (i_Col > 0 & i_Col <= Height))
                {
                    return m_GameBoard[i_Row - 1, i_Col - 1];
                }
                else
                {
                    throw new IndexOutOfRangeException();
                }
            }

            set
            {
                // Check if the location is within the bounds
                if ((i_Row > 0 && i_Row <= Height + GameBoard.k_NumOfBufferLines) && (i_Col > 0 & i_Col <= Height))
                {
                    // If a current pawn is overriden
                    // Decrease the pawn count of the matching type
                    if (m_GameBoard[i_Row - 1, i_Col - 1] != null)
                    {
                        if (m_GameBoard[i_Row - 1, i_Col - 1].Type == eGamePawnType.White)
                        {
                            m_WhitePawns--;
                        }
                        else
                        {
                            m_BlackPawns--;
                        }
                    }
                    
                    // If a real pawn was placed, increase its count
                    if (value != null)
                    {
                        if (value.Type == eGamePawnType.White)
                        {
                            m_WhitePawns++;
                        }
                        else
                        {
                            m_BlackPawns++;
                        }
                    }

                    // Put the value in
                    m_GameBoard[i_Row - 1, i_Col - 1] = value;
                }
                else
                {
                    throw new IndexOutOfRangeException();
                }
            }
        }

        /// <summary>
        ///  Returns the pawn on the current location
        /// </summary>
        /// <param name="i_Col">The column</param>
        /// <param name="i_Row">The row</param>
        /// <returns>The currnt pown</returns>
        public GamePawn this[char i_Col, int i_Row]
        {
            get
            {
                // Convert the column name to index
                int colNumber = convertColumnNameToIndex(i_Col);

                // Return the pawn in the position
                return this[colNumber, i_Row];
            }
        }

        /// <summary>
        /// Returns the pawn on the current location
        /// </summary>
        /// <param name="i_Location">The location of the pawn</param>
        /// <returns>The pawn in the location</returns>
        public GamePawn this[Location i_Location]
        {
            get
            {
                // Return the pawn in the position
                return this[i_Location.Column, i_Location.Row];
            }

            set
            {
                this[i_Location.Column, i_Location.Row] = value;
            }
        }

        /// <summary>
        /// Convert the column from a letter to a number
        /// </summary>
        /// <param name="i_ColName">The letter that the user insert</param>
        /// <returns>The number of the column</returns>
        private int convertColumnNameToIndex(char i_ColName)
        {
            // Return the index of the column - Where k_BoardStartColumn is the first one
            return Char.ToUpper(i_ColName) - GameBoard.k_BoardStartColumn + 1;
        }
    }
}
